<template>
  <section v-loading="loading" class="update-img">
    <section class="source">
      <ul class="source-list">
        <li v-for="(item, index) in list" :key="index" class="list-item">
          <el-image :src="item">
            <div slot="error" class="image-slot">
              <template>
                <div class="icon-wrap">
                  <i class="el-icon-plus avatar-uploader-icon" />
                </div>
              </template>
            </div>
          </el-image>
          <section class="item-status">
            <i class="el-icon-check" />
          </section>
          <section class="item-action">
            <i class="el-icon-zoom-in" @click="handleView(item)" />
            <i class="el-icon-delete" @click="handleRemove(index)" />
          </section>
        </li>
      </ul>
      <section class="source-card">
        <i class="el-icon-plus" />
        <section class="card-input">
          <input id="change" ref="inputImg" type="file" multiple="multiple" accept="image" name value @change="handleUpload($event)">
        </section>
      </section>
    </section>
  </section>
</template>

<script>
import axios from 'axios'
import Qs from 'qs'
import CryptoJS from 'crypto-js'
export default {
  name: 'UpdateImg',
  props: {
    imgData: {
      type: Array,
      default() {
        return []
      }
    },
    index: {
      type: Number,
      default: ''
    },
    limit: {
      type: Number,
      default: 9
    }
  },
  data() {
    return {
      loading: false,
      view: '/API-DOCUMENT/document/file/download?id=',
      list: [],
      file: [],
      worker: null,
      chunkSize: 10 * 1024 * 1024
    }
  },
  created() {
    this.initImg()
  },
  methods: {
    handleView(src) {
      this.$emit('imgShowView', src)
    },
    /*
    * 初始化图片
    * */
    initImg() {
      for (let i = 0; i < this.imgData.length; i++) {
        this.list.push(this.view + this.imgData[i])
      }
    },
    /**
     * 计算签名
     */
    getSign(params) {
      const timeSta = new Date().Format('yyyyMMddhhmmss')
      const signObj = this.objKeySort(params)
      var count = 0 // 对象属性的个数
      let signStr = ''
      for (const i in signObj) {
        if (signObj.hasOwnProperty(i)) {
          count++
        }
      }
      let index = 0
      for (const key in signObj) {
        index += 1
        signStr += key + '=' + signObj[key] || ''
        if (index !== count) {
          signStr += '|'
        }
      }
      signStr += ':' + timeSta + ':document'
      return { sign: signStr, time: timeSta }
    },
    objKeySort(obj) { // 排序的函数
      var newkey = Object.keys(obj).sort() // 先用Object内置类的keys方法获取要排序对象的属性名，再利用Array原型上的sort方法对获取的属性名进行排序，newkey是一个数组
      var newObj = {} // 创建一个新的对象，用于存放排好序的键值对
      for (var i = 0; i < newkey.length; i++) { // 遍历newkey数组
        newObj[newkey[i]] = obj[newkey[i]] // 向新创建的对象中按照排好的顺序依次增加键值对
      }
      return newObj // 返回排好序的新对象
    },
    handleUpload(e) {
      if (e.target.files.length <= 0) {
        return
      }
      if (this.list.length + e.target.files.length > this.limit) {
        this.$message.error(`最多上传${this.limit}张照片`)
        this.$refs.inputImg.value = ''
        return false
      }
      if (e.target.files[0].type.indexOf('image') === -1) {
        this.$message({
          message: '请上传图片类型的文件！',
          type: 'warning',
          duration: 2000
        })
        return false
      }
      this.file = e.target.files
      this.loading = true
      this.upload()
    },

    handleSubmit() {
      this.$emit('successCBK', this.list)
      this.list = []
    },

    handleRemove(i) {
      this.imgData.splice(i, 1)
      this.list.splice(i, 1)
      const obj = {
        imgs: this.imgData,
        index: this.index
      }
      this.$emit('getImgId', obj)
    },

    /**
     * 上传图片
     */
    async upload() {
      for (const i in this.file) {
        const tfile = this.file[i]
        const chunknum = Math.ceil(tfile.size / this.chunkSize)
        const name = tfile.name || ''
        const size = tfile.size
        const md5 = await this.calculateHash(this.createFileChunk(tfile))
        const obj = {
          md5: md5,
          fileName: name,
          fileSize: size,
          chunkNum: chunknum,
          chunkSize: this.chunkSize
        }
        await this.getUUID(obj).then((data) => {
          const chunks = this.createFileChunk(tfile)
          tfile['UUID'] = data
          tfile['chunks'] = chunks.map(({ file }, index) => ({
            UUID: data,
            fileName: name,
            index,
            chunk: file,
            size: file.size
          }))
          this.uploadChunks(tfile)
        })
      }
    },
    /**
     * 提交文件获取UUID
     */
    async getUUID(params) {
      return new Promise((resolve, reject) => {
        const sign = this.getSign(params)
        axios.post('/API-DOCUMENT/document/file/insert', params, {
          transformRequest: [(data) => {
            data = Qs.stringify(data)
            return data
          }],
          headers: {
            ttsxbwqm: CryptoJS.SHA256(sign.sign).toString(),
            ttsxsjzc: sign.time
          }
        }).then((res) => {
          if (res.data.code === 200 || res.data.code === '200') {
            resolve(res.data.data)
          } else {
            this.$message.warning(res.data.msg || '文件读取失败')
            resolve()
          }
        }).catch((err) => {
          this.loading = false
          reject(err)
        })
      })
    },

    /**
     * 创建文件切片
     */
    createFileChunk(file, size = this.chunkSize) {
      const list = []
      var count = 0
      while (count < file.size) {
        list.push({
          file: file.slice(count, count + size)
        })
        count += size
      }
      return list
    },

    /**
     * 生成文件 hash（web-worker）
     */
    calculateHash(chunks) {
      return new Promise((resolve) => {
        this.worker = new Worker('./hash.js')
        this.worker.postMessage({ chunks })
        this.worker.onmessage = (e) => {
          const { hash } = e.data
          if (hash) {
            resolve(hash)
          }
        }
      })
    },

    /**
     * 将切片传输给服务端
     */
    async uploadChunks(file) {
      var chunkData = file.chunks
      return new Promise(async(resolve, reject) => {
        const requestDataList = chunkData.map(({ chunk, UUID, fileName, index }) => {
          const formData = new FormData()
          formData.append('UUID', UUID)
          formData.append('file', chunk)
          formData.append('fileIndex', index) // 文件名使用切片的下标
          return { formData, index, fileName, UUID }
        })

        try {
          await this.sendRequest(requestDataList, chunkData)
        } catch (error) {
          return
        }

        try {
          await this.mergeRequest(file)
          resolve()
        } catch (error) {
          this.loading = false
          reject()
        }
      })
    },

    /**
     * 上传切片
     */
    sendRequest(forms, chunkData) {
      return new Promise((resolve, reject) => {
        const formInfo = forms.shift()
        const formData = formInfo.formData
        const sign = this.getSign({ fileIndex: formData.get('fileIndex'), UUID: formData.get('UUID') })
        axios.post('/API-DOCUMENT/document/file/chunk', formData, {
          headers: {
            'Content-Type': 'multipart/form-data',
            ttsxbwqm: CryptoJS.SHA256(sign.sign).toString(),
            ttsxsjzc: sign.time
          }
        }).then((res) => {
          if (res.data.code === 200 || res.data.code === '200') {
            resolve()
          } else {
            this.loading = false
            this.$message({
              message: '图片上传失败',
              type: 'warning',
              duration: 2000
            })
          }
        }).catch((e) => {
          this.loading = false
          this.$message({
            message: '图片上传失败',
            type: 'warning',
            duration: 2000
          })
        })
      })
    },

    /**
     * 通知服务端合并切片
     */
    mergeRequest(file) {
      const _this = this
      return new Promise((resolve, reject) => {
        const sign = this.getSign({ UUID: file.UUID })
        axios.post('/API-DOCUMENT/document/file/merge', { UUID: file.UUID }, {
          transformRequest: [(data) => {
            data = Qs.stringify(data)
            return data
          }],
          headers: {
            ttsxbwqm: CryptoJS.SHA256(sign.sign).toString(),
            ttsxsjzc: sign.time
          }
        }).then((res) => {
          if (res.data.code === 200 || res.data.code === '200') {
            this.loading = false
            this.list.push(this.view + res.data.data.id)
            this.imgData.push(res.data.data.id)
            const obj = {
              imgs: this.imgData,
              index: this.index
            }
            this.$emit('getImgId', obj)
            this.$refs.inputImg.value = ''
            resolve()
          } else {
            this.loading = false
            this.$message({
              message: resolve.data.msg || '服务器繁忙请稍后再试。',
              type: 'error',
              duration: 200
            })
            resolve()
          }
        }).catch((e) => {
          this.loading = false
          _this.$message({
            message: '服务器繁忙请稍后再试。',
            type: 'error',
            duration: 200
          })
          reject()
        })
      })
    }
  }
}
</script>
<style lang="scss">
.update-img {
  border: 1px solid #ebebeb;
  border-radius: 3px;
  transition: 0.2s;
  .source {
    padding: 12px;
    .source-list {
      margin: 0;
      padding: 0;
      list-style: none;
      display: inline;
      vertical-align: top;
      .list-item {
        overflow: hidden;
        background-color: #fff;
        border: 1px solid #c0ccda;
        border-radius: 6px;
        box-sizing: border-box;
        width: 148px;
        height: 148px;
        margin: 0 8px 8px 0;
        display: inline-block;
        transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1);
        font-size: 14px;
        color: #606266;
        line-height: 1.8;
        margin-top: 5px;
        position: relative;
        .item-status {
          position: absolute;
          right: -15px;
          top: -6px;
          width: 40px;
          height: 24px;
          background: #13ce66;
          text-align: center;
          transform: rotate(45deg);
          box-shadow: 0 0 1pc 1px rgba(0, 0, 0, 0.2);
          > i {
            color: #fff;
            font-size: 12px;
            margin-top: 11px;
            transform: rotate(-45deg);
          }
        }

        .item-action {
          position: absolute;
          width: 100%;
          height: 100%;
          left: 0;
          top: 0;
          cursor: default;
          text-align: center;
          color: #fff;
          opacity: 0;
          font-size: 20px;
          background-color: rgba(0, 0, 0, 0.5);
          transition: opacity 0.3s;
          display: flex;
          align-items: center;
          justify-content: center;
          > i {
            cursor: pointer;
            &:last-child{
              margin-left: 20px;
            }
          }
        }

        &:hover {
          .item-action {
            opacity: 1;
          }
        }
      }
    }

    .source-card {
      display: inline-block;
      text-align: center;
      outline: none;
      background-color: #fbfdff;
      border: 1px dashed #c0ccda;
      border-radius: 6px;
      box-sizing: border-box;
      width: 148px;
      height: 148px;
      line-height: 146px;
      position: relative;
      > i {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        font-size: 28px;
        color: #8c939d;
      }
      .card-input {
        cursor: pointer;
        width: 100%;
        height: 100%;
        position: absolute;
        opacity: 0;
        top: 50%;
        transform: translateY(-50%);
        z-index: 2;
        input {
          cursor: pointer;
          display: block;
          width: 100%;
          height: 100%;
        }
      }
    }
  }
}
</style>
